﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Collections.Specialized;
using System.Collections;
namespace ServerUygulamasi
{
    //Kodlayan : Said AYDOĞAN
    //Anlatan  : Said AYDOĞAN
    public partial class ServerForm : Form
    {
        public ServerForm()
        {
            InitializeComponent();
            //Eventlerin tetiklendiğinde çalışmasını istediğimiz metodlar
            MesajGeldiEventi += new DgMesajGeldi(ServerForm_MesajGeldiEventi);
            YeniClientBaglandi += new ClientBaglandi(ServerForm_YeniClientBaglandi);
            BaglantiKoptuEvent += new DgClientBaglantiKoptu(ServerForm_BaglantiKoptuEvent);
        }
           

        private void button1_Click(object sender, EventArgs e)
        {

        }
        /// <summary>
        /// Yeni bir client bağlandığında çalışmasını istediğimiz delegate ve event.
        /// </summary>
        /// <param name="Yclient"> Bağlanan clientin özelliklerini barındıran sınıf.</param>
        /// <param name="Scket"> Bağlanan clientin soketi.</param>
        public delegate void ClientBaglandi(ClientParametreleri Yclient,Socket Scket);
        public event ClientBaglandi YeniClientBaglandi;

        /// <summary>
        /// Yeni mesaj geldiğinde tekiklenmesini istediğimiz delegate ve event.
        /// </summary>
        /// <param name="Pclient">Mesaj gönderen clientin özelliklerini barındıran sınıf. </param>
        /// <param name="Mevent">Mesaj bilgilerini içeren sınıf.</param>
        public delegate void DgMesajGeldi(ClientParametreleri Pclient, MesajEvent Mevent);
        public event DgMesajGeldi MesajGeldiEventi;

        /// <summary>
        /// Client bağlantısı kesildiğinde tetiklenmesini istediğimiz delegate ve event.
        /// </summary>
        /// <param name="Pclient">Bağlantısı kesilen clientin bilgilerini içeren sınıf.</param>
        public delegate void DgClientBaglantiKoptu(ClientParametreleri Pclient);
        public event DgClientBaglantiKoptu BaglantiKoptuEvent;

        //Clientlerin bilgilerinin  ve soketlerinin tutulduğu listeler.
        List<ClientParametreleri> Clientler = new List<ClientParametreleri>();
        List<Socket> ClientSoketler = new List<Socket>();
        
        /// <summary>
        /// Server mantığı içerisinde sürekli dinlemede olan delagate.
        /// </summary>
        /// <param name="Ip">Server Ip si</param>
        /// <param name="port">Server Portu</param>
       
        public delegate void ServerDelegate(IPAddress Ip,int port);
        //Clientlere ID vermek için tanımladık.
        int SonID = 0;
        TcpListener Dinleyici;
        void ServerBaslat(IPAddress IP, int port)
        {
            //Tcplistener sınıfı 3 farklı şekilde kurulabilir biz ip,port şeklinde kurduk. Sadece port da olabilir.
            Dinleyici = new TcpListener(IP, port);
            //Dinleyicimizi başlatıyoruz.
            Dinleyici.Start();
            //Server durumu hakkında bilgilendirme yapıyoruz..
            lblDurum.ForeColor = System.Drawing.Color.Green;
            lblDurum.Text = "Başlatıldı";
            ServerCalisiyor = true;
            BilgiYaz("Sunucu Başlatıldı.");

            //Eğer server çalışıyorsa sürekli yeni client beklesini istiyoruz.
            while (ServerCalisiyor)
            {
                try
                {
                    //Soket bağlantısı yapılıyoruz. 

                    //Burası dikkat edilmesi gereken noktlardan birisi.
                    //Çünkü Client ile sokent bağlantısı sağlandıktan sonra kod ilerleyecektir. 
                    //Yani sunucu başlat dedik ve bu aşamaya kadar gelen işlemler dinlenilen port'a bağlanıldığında  devam edecektir.                 

                    Socket ClientSoket = Dinleyici.AcceptSocket();
                    //Bir diğer nokta ise bağlantı sağlandığından dolayı veri akışını sürekli takip etmek için YeniClientBaglandi eventini tetiliyoruz.
                    YeniClientBaglandi.BeginInvoke(new ClientParametreleri(SonID), ClientSoket, null, null);
                    //Client Bağlandığı için ID değerini 1 artırıyoruz.
                    SonID++;
                }
                catch
                {
                    //Server durdurma işleminde olası hatayı engellemek için burda da Stop() Metodunu kullandık.
                    Dinleyici.Stop();
                    break;
                }

            }
        }
        //Server ın çalışma durumunu belirtiyoruz.
        bool ServerCalisiyor = true;
        void ServerForm_YeniClientBaglandi(ClientParametreleri Yclient, Socket ClientSoket)
        {
            //Yeni bağlanan clientleri uygun listlere ekliyoruz.
            Clientler.Add(Yclient);
            ClientSoketler.Add(ClientSoket); 
            //Server çalışıyorsa clientlerin soketlerini sürekli dinliyoruz.
            while (ServerCalisiyor)
            {
                //Eğer sokete bağlanma işlemi gerçekleşmişse ilerliyoruz.
                //Bunu while in üstünde de tanımlayabiliriz fark etmez : ) Burda sürekli bağlandı mı daha önce diye bakar üstünde tanımlarsak 1 kere bakar daha önce bağlandı mı diye. Zaten aktif bağlı olma durumu bu şekilde dönmez.
                if (ClientSoket.Connected)
                {
                    //Bağlanan istemci hakkında network kaynaklarına ulaşıyoruz.
                    NetworkStream ntworkStream = new NetworkStream(ClientSoket);
                    //Gelen kağnağı okuyoruz.         
                    try
                    {          
                        //Kullanılabilir uzunlukta byte tanımlaması yapıyoruz.
                            byte[] gelenveri = new byte[ClientSoket.Available];
                            //Dikkat : Client tarafından bağlanıldıktan sonra veri gelmesi beklenir.İlerleme olmaz.
                            ClientSoket.Receive(gelenveri);
                        //Eğer gelen bit uzunluğu 0 dan büyükse ilerlemesini sağlıyoruz.
                        //Böylece boş yere mesaj değerlendirmesi yapılmaz.
                            if (gelenveri.Length > 0)
                            {
                                //Bitler okunarak karakter dizi haline getiriliyor ve mesajı oluşturuyoruz.  
                                //Dikkat : Bazı encoding şekilleri daha denedim fakat türkçe karakter problemi yaşadım bundan dolayı en sağlıklı olarak buna(UTF8 'e) karar verdim.
                                char[] gelenKarakterDizisi = System.Text.Encoding.UTF8.GetChars(gelenveri);
                                string strMesaj = "";                    
                                //Mesajı okuma işlemi daha değişik şekillerde de yapılabilir..
                                for (int i = 0; i < gelenKarakterDizisi.Length; i++)
                                {
                                    strMesaj += gelenKarakterDizisi[i].ToString();
                                }
                                //Okunan mesajla  ve gelen Client Parametresiyle birlikte eventi i tetikliyoruz.             
                                //Anlatıma aynen derleme işlemi gibi gidiyoruz. ServerForm_MesajGeldiEventi ' ne geçiniz. Ya da catch a düştüyse ne olmuş onu inceleyin.
                                MesajGeldiEventi(Yclient, new MesajEvent(strMesaj));
                            }
                    }
                    catch
                    {   
                        //Eğer işlem gerçekleştirilemiyorsa bağlantı kesilmiş demektir.
                        //Bunun için aktif soketi kapatıyor eventimizi tetikliyoruz.
                        ClientSoket.Close();
                        BaglantiKoptuEvent(Yclient);
                        break;
                    }
                }
                else
                {
                    //Eğer soket daha önce bağlanmadıysa ki genelde bu metoda kadar gelenler hep bağlı olduğu halde olur ya bağlı olmadığı durumda da soket bağlantısı kesilip event tetiklenir.
                    ClientSoket.Close();
                    BaglantiKoptuEvent(Yclient);
                    break;
                }

            }
        }

        void ServerForm_MesajGeldiEventi(ClientParametreleri ClientParam, MesajEvent Mevent)
        {
                 //Nesnelere windows form da Ana thread müdahele içerisinde olduğu için bizim de delegate / event vb metodlarında müdahele edebilmemiz için bir nevi sahiplik yetkisi alıyoruz. 
            Invoke(new DgMesajGeldi(MesajDegerlendirme), ClientParam, Mevent);
           
        }
        void MesajDegerlendirme(ClientParametreleri ClientParam, MesajEvent Mevent)
        {
            //Anlatıma "MesajCoz" den devam ediyoruz.
            NameValueCollection parametreler = MesajCoz(Mevent.Mesaj);
            //Eğer mesaj geçersizse metoddan çıkıyoruz..
            if (parametreler == null || parametreler.Count < 1)
            {
                return;
            }
            //Çözülen Mesaj'a göre komut incelenir ve gerekli işlem yapılır.
            try
            {
                //Parametrelerde "komut" u alıyoruz.
                switch (parametreler["komut"])
                {
                        //Komut=Giris ise Client'in adını alıyoruz ve Clientin Sunucu tarafındaki adına yazıyoruz.
                        //Mesajı gönderen Client parametre olarak GirisYap ' a gönderiyoruz.. 
                        //Anlatıma GirisYap dan devam ediyoruz..
                    case "Giris":
                        //parametreler: nick
                        ClientParam.ClientAdi = parametreler["isim"]; GirisYap(ClientParam);
                        break;
                    case "MesajYolla":
                        {                            
                            ServerMesajYolla(int.Parse(parametreler["Kime"]), "komut=GelenMesaj&Kimden=" + ClientParam.ClientAdi + "&Mesaj=" + parametreler["Mesaj"]);
                            // [ Burayı atlıyoruz(Çok açık zaten) ]
                            ClientMesajRaporu(int.Parse(parametreler["Kime"]), ClientParam.ClientAdi, parametreler["Mesaj"]);
                        }
                        break;
                    case "Cikis":
                        ClientCikisiYap(parametreler["Kim"]);
                        break;
                }
            }
            catch 
            {

            }

        }
        private NameValueCollection MesajCoz(string mesaj)
        {
            try
            {
                //& işaretine göre mesajı bölüyoruz ve diziye alıyoruz.
                string[] parametreler = mesaj.Split('&');
                //index - key - value 3 lüsünü barındıran "NameValueCollection" sınıfını kullanıyoruz.  
                NameValueCollection nvcParametreler = new NameValueCollection(parametreler.Length);  
                //Bölünen mesajı da = karakterine göre key-value eşlemesi yapıyoruz.
                foreach (string parametre in parametreler)
                {
                    string[] esitlikParametreleri = parametre.Split('=');
                    //Anahtar olarak eşitliğin sol tarafını, Değer olarak eşitliğin sağ tarafını kullanıyoruz
                    nvcParametreler.Add(esitlikParametreleri[0], esitlikParametreleri[1]);
                }
                //Koleksiyonu gönderdiğimize göre anlatıma "MesajDegerlendirme" de kaldığımız yerden devam ediyoruz..
                return nvcParametreler;
            }
            catch (Exception)
            {
                //Geçersiz mesaj inceleniyorsa boş değer dönderiyoruz.
                return null;
            }
        }

        void ServerForm_BaglantiKoptuEvent(ClientParametreleri Pclient)
        {
            //if (!AslindaKopmadi)
                BilgiYaz("\"" + Pclient.ClientAdi + "\" Sunucudan Ayrıldı.");           
             ClientSoketler.RemoveAt(Pclient.ClientID);
             //if (!AslindaKopmadi)
                 ServerMesajYolla(-1, "komut=BaglantiKoptu&Kim=" + Pclient.ClientID);
             Clientler.RemoveAt(Pclient.ClientID);
             //if (!AslindaKopmadi)
                 lstKullanici.Items.RemoveAt(Pclient.ClientID);
             SonID--;
            ClientleriYerlestir();
        }
        void ClientleriYerlestir()
        {
            for (int i = 0; i < Clientler.Count; i++)
            {
                Clientler[i].ClientID = i;                
            }
        }     
        private void ClientMesajRaporu(int GonderilenID, string Kimden, string Mesaj)
        {
            string Kime = " (Toplu Mesaj)";
            if (GonderilenID != -1)
            {
                foreach (ClientParametreleri cpam in Clientler)
                {
                    if (cpam.ClientID == GonderilenID)
                        txtClientMesajlar.Text += Kimden + " ==> " + cpam.ClientAdi + " : " + Mesaj + "\n";
                }
            }
            else if(GonderilenID == -1)
            {
                txtClientMesajlar.Text += Kimden + " ==> : " + Mesaj + Kime + "\n";
            }
        }
    
        private void GirisYap(ClientParametreleri ClientParam)
        {          
            int AyniClientSayisi = 0;
            //Giriş yapan client ' i  ID siyle birlikte kullanıcılar listesine ekliyoruz.
            lstKullanici.Items.Add(ClientParam.ClientID + " - " + ClientParam.ClientAdi);
            //Sırayla Clientler okunuyor.
            foreach (ClientParametreleri cpam in Clientler)
            {
              
                if (cpam.ClientAdi == ClientParam.ClientAdi)
                {
                    AyniClientSayisi++;                   
                    if (AyniClientSayisi > 1)
                    {   //Eğer aynı isimle 1 den fazla client varsa(1'i zten kendi) IsimDegis komutu  gönderiyoruz.
                        ServerMesajYolla(ClientParam.ClientID, "komut=IsimDegis");
                        //Ve bağlı olduğu soketi kapatıyoruz ki tekrar erişemesin yeniden bağlanmak zorunda kalsın.
                        ClientSoketler[ClientParam.ClientID].Close();                        
                        BilgiYaz("Aynı nickle giriş  tespit edildi ve bağlantısı kesildi.");
                        return;
                    }
                }
            }           
            //Eğer aynı nickte 1 den fazla kimse yoksa bağlanan client ' e kullanıcı listesi yolluyoruz.
            KullaniciListesiYolla(); // [Metod zaten basit isteyen inceleyebilir.. (Biz girmiyoruz.) ]
            BilgiYaz("Yeni İstemci \"" + ClientParam.ClientAdi + "\" ismiyle giriş yaptı.");
          
            //Evet.Derleyicinin gözünden şuan 1 client i bağladık :)
            // Sırada "MesajDegerlendirme" de MesajYolla komutu var. Anlatıma "ServerMesajYolla" metodundan   devam ediyoruz..
        }
        void KullaniciListesiYolla()
        {
            string isimler = string.Empty;
            for (int i = 0; i < Clientler.Count; i++)
            {
                isimler += Clientler.ToArray()[i].ClientAdi + ",";

            }
            ServerMesajYolla(-1, "komut=listele&isimler=" + isimler);
        }
        void ServerMesajYolla(int ClientID, string Mesaj)
        {
            try
            {
                //Gönderilecek mesaj bit haline dönüştürülüyor..
                NetworkStream strmGonderici;
                System.Text.Encoding encoding = System.Text.Encoding.UTF8; ;
                byte[] ba = encoding.GetBytes(Mesaj);

                if (ClientID == -1)
                {
                    //Parametre olarak ID = -1 gelmişse bütün clientlere mesaj yollatıyoruz..
                    foreach (Socket sckt in ClientSoketler)
                    {
                        strmGonderici = new NetworkStream(sckt);
                        strmGonderici.Write(ba, 0, ba.Length);
                        strmGonderici.Flush();
                    }
                }
                else
                {
                    //Eğer sadece 1 client e mesaj yollanacaksa Parametre olarak gelen ID i kullanarak, Gönderilecek  Client in soketine erişiyoruz ve mesajı gönderiyoruz.
                    strmGonderici = new NetworkStream(ClientSoketler[ClientID]);
                    strmGonderici.Write(ba, 0, ba.Length);
                    strmGonderici.Flush();
                }
            }
            catch
            {
                BilgiYaz("Server Mesaj Gönderemedi.. (İnternet Durumunu Kontrol ediniz..)");
            }
            // Geriye komut olarak "CikisYap" kaldığına göre ClientCikisiYap metoduna gidiyoruz.

        }
        private void ClientCikisiYap(string ClientAd)
        {
            //Parametre olarak gönderilen ClientAdi clientlerde aranır ve bulunan client in bağlantısı kesilir.
            foreach (ClientParametreleri cpam in Clientler)
            {
                if (cpam.ClientAdi == ClientAd)
                    BaglantiKoptuEvent(cpam);
            }
            //Şimdiye kadar client giriş yaptı , başkalarına mesaj yolladı ve çıkış yaptı..
            //Sıra da Server ı Durdurma işlemi var.
        }
        private void btnDurdur_Click(object sender, EventArgs e)
        {
            //Bütün client lere Server ı durdurduğumuzu belirtiyoruz.
            ServerMesajYolla(-1, "komut=ServerDurdu");
            //Server ın sonsuz döngülerden çıkmasını sağlıyoruz.
            ServerCalisiyor = false;
            //Dinleyicimizi kapatıyoruz.
            Dinleyici.Stop();
            //Listler vs temizleniyor kullanıcı bilgilendiriliyor. (Başa dönüyoruz  bir nevi)
            Clientler.Clear();
            ClientSoketler.Clear();
            lstKullanici.Items.Clear();
            SonID = 0;
            txtClientMesajlar.Text = "";
            BilgiYaz("Sunucu Durduruldu..");
            lblDurum.ForeColor = System.Drawing.Color.Red;
            lblDurum.Text = "Durduruldu";

            //Server'ı da başlattık ve durdurduk.
            //Bu programın kod anlatımı burda bitmiştir. Başka programlarda görüşmek üzere.
                                           //Kolay Gelsin.
                                          //Said AYDOĞAN

        }

       
        
        private void btnBaslat_Click(object sender, EventArgs e)
        {            
            //Sürekli dinleme yapmasını sağlayan metodu Asenkron olarak çalıştırıyoruz.
            ServerDelegate dd = new ServerDelegate(ServerBaslat);
            dd.BeginInvoke(IPAddress.Any,int.Parse(txtPort.Text),null,null);
            //Anlatıma "ServerBaslat" dan devam..
  
        }
        bool MesajlarGosteriliyor = false;
        private void lnkMGstrGzle_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            if (MesajlarGosteriliyor)
            {
                this.Size = new Size(584, 260);

                MesajlarGosteriliyor = false;
            }
            else
            {
                this.Size = new Size(584, 408);
                MesajlarGosteriliyor = true;
            }          
        }

        private void ServerForm_Load(object sender, EventArgs e)
        {
            //Çapraz iş parçacıklarının (threadların) nesnelere erişiminin denetlenmesini devre dışı ediyoruz
            CheckForIllegalCrossThreadCalls = false;
            //Anlatıma "btnBaslat_Click" den devam ediyoruz.
        }

       
        void BilgiYaz(string Mesaj)
        {
            txtServerBilgi.Text += Mesaj + "\n";
        
        }

        private void temizleToolStripMenuItem_Click(object sender, EventArgs e)
        {
            txtServerBilgi.Text = "";
        }

        private void istemciMesajlaşmalarınıTemizleToolStripMenuItem_Click(object sender, EventArgs e)
        {
            txtClientMesajlar.Text = "";
        }     
    }
    //Delegatelerde kullandığımız eventler
    public class MesajEvent : EventArgs
    {
        public string Mesaj
        {
            get { return mesaj; }
            set { mesaj = value; }
        }
        private string mesaj;

        public MesajEvent(string mesaj)
        {
            this.mesaj = mesaj;
        
        }
    }  
    public class ClientParametreleri:EventArgs
    {
        private int _ClientID;

        public int ClientID
        {
            get { return _ClientID; }
            set { _ClientID = value; }
        }
        private string _ClientAdi;

        public string ClientAdi
        {
            get { return _ClientAdi; }
            set { _ClientAdi = value; }
        }

        public ClientParametreleri(int ID)
        {
            this._ClientID = ID;           
        }
    }
}
